﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Drawing;
using System.Data;
using System.Linq;
using System.Text;
using System.Windows.Forms;
using System.IO;
using System.Diagnostics;
using System.Threading;

using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;

using CPXMenu;

using CPXMenuEditor.Forms;

namespace CPXMenuEditor.Controls
{
    public partial class DefaultContentBrowser : UserControl
    {
        private string contentDirectory;
        private ContentManager contentManager;
        private DirectoryNode rootNode;
        private MenuProject menuProject;
        private ContentNode selectedContentFile = null;
        private Type contentType;
        private bool initialized = false;
        private bool initializing = false;
        private BaseNode selectedNode = null;

        /// <summary>
        /// Gets the currently selected content file
        /// </summary>
        [Browsable(false)]
        public ContentNode SelectedContentFile
        {
            get { return selectedContentFile; }
            set 
            {                
                ContentNode prevFile = selectedContentFile;
                selectedContentFile = value;

                if (!initialized)
                    return;

                if (object.ReferenceEquals(prevFile, selectedContentFile))
                    return;

                SelectedNode = value;
                
                OnSelectedContentChanged();
            }
        }

        [Browsable(false)]
        public BaseNode SelectedNode
        {
            get { return selectedNode; }
            set
            {
                BaseNode prevNode = selectedNode;
                selectedNode = value;

                if (!initialized)
                    return;

                if (object.ReferenceEquals(prevNode, selectedNode))
                    return;

                if (value == null)
                    treeView.SelectedNode = null;
                else                
                    treeView.SelectedNode = FindTreeNode(treeView.Nodes, value);

                SelectedContentFile = value as ContentNode;

                OnSelectedNodeChanged();
            }
        }

        [Browsable(false)]
        public bool Initialized
        {
            get { return initialized; }
        }

        [Browsable(false)]
        public bool Initializing
        {
            get { return initializing; }
        }

        [Browsable(false)]
        public DirectoryNode RootNode
        {
            get { return rootNode; }
        }

        public DefaultContentBrowser()
        {
            InitializeComponent();

            CenterProgressBar();
        }

        public void Initialize(string selectedContentFile, Type contentType, IServiceProvider services, string contentDirectory, MenuProject menuProject)
        {
            if (initializing)
                return;

            initialized = false;
            initializing = true;
            treeView.Enabled = false;
            treeView.Nodes.Clear();

            this.contentType = contentType;
            this.contentDirectory = contentDirectory;
            this.contentManager = new ContentManager(services, contentDirectory);
            this.menuProject = menuProject;

            rootNode = new DirectoryNode();
            DirectoryInfo info = new DirectoryInfo(contentDirectory);
            rootNode.Path = info.FullName;
            rootNode.Name = info.Name;

            // Kick off the worker thread
            Thread thread = new Thread(ThreadStart);
            thread.Start();

            while (!thread.Join(0))
            {
                Application.DoEvents();
                Thread.Sleep(5);
            }

            progressBar.Visible = false;
            loadingLabel.Visible = false;

            // Dispose of the content
            contentManager.Dispose();
            contentManager = null;
            GC.Collect();            
            
            BuildTree(treeView.Nodes, rootNode);

            treeView.Enabled = true;
            this.Cursor = Cursors.Default;

            initialized = true;
            initializing = false;

            if (LoadingComplete != null)
                LoadingComplete(this, EventArgs.Empty);

            SelectedContentFile = FindContentNode(selectedContentFile);
        }    

        protected virtual void OnSelectedContentChanged()
        {
            if (SelectedContentChanged != null)
                SelectedContentChanged(this, EventArgs.Empty);
        }

        protected virtual void OnSelectedNodeChanged()
        {
            if (SelectedNodeChanged != null)
                SelectedNodeChanged(this, EventArgs.Empty);
        }

        protected override void OnResize(EventArgs e)
        {
            base.OnResize(e);

            CenterProgressBar();
        }

        private ContentNode FindContentNode(string contentFile)
        {
            TreeNode treeNode = FindTreeNodeWithContent(treeView.Nodes, contentFile);
            if (treeNode == null)
                return null;
            else
                return treeNode.Tag as ContentNode;
        }

        private TreeNode FindTreeNodeWithContent(TreeNodeCollection nodes, string contentFile)
        {
            foreach (TreeNode childNode in nodes)
            {
                if (childNode.Tag is ContentNode && ((ContentNode)childNode.Tag).Path == contentFile)
                    return childNode;
                else
                {
                    TreeNode result = FindTreeNodeWithContent(childNode.Nodes, contentFile);
                    if (result != null)
                        return result;
                }
            }

            return null;
        }

        private TreeNode FindTreeNode(TreeNodeCollection nodes, BaseNode node)
        {
            foreach (TreeNode childNode in nodes)
            {
                if (object.ReferenceEquals(childNode.Tag, node))
                    return childNode;
                else
                {
                    TreeNode result = FindTreeNode(childNode.Nodes, node);
                    if (result != null)
                        return result;
                }
            }

            return null;
        } 

        private void CenterProgressBar()
        {
            progressBar.Left = 50;
            progressBar.Width = this.Width - 100;
            progressBar.Top = (this.Height / 2) - (progressBar.Height / 2);
            loadingLabel.Top = progressBar.Top - 20;
            loadingLabel.Left = (this.Width / 2) - (loadingLabel.Width / 2);
        }

        /// <summary>
        /// Entry point for the worker thread.  Calls EnumerateContent.
        /// </summary>
        private void ThreadStart()
        {
            EnumerateContent(rootNode);
        }

        /// <summary>
        /// Enumerates all loadable content for types in contentTypes, and stores
        /// the resulting tree in contentTrees
        /// </summary>
        private void EnumerateContent(DirectoryNode node)
        {            
            if (!Directory.Exists(node.Path))
                return;

            // Rip through all of the .xnb files
            string[] contentFiles = Directory.GetFiles(node.Path, "*.xnb");
            foreach (string contentFile in contentFiles)
            {
                string contentName = Path.GetFileNameWithoutExtension(contentFile);
                string loadName;
                if (node == rootNode)
                    loadName = contentName;
                else
                    loadName = Path.GetDirectoryName(contentFile).Substring(rootNode.Path.Length + 1) + "\\" + contentName;

                object content = TryLoadContent(loadName);
                if (content != null)
                {
                    ContentNode contentNode = new ContentNode();
                    contentNode.Path = loadName;
                    contentNode.Name = contentName;
                    contentNode.Type = content.GetType();
                    node.ContentNodes.Add(contentNode);
                }
            }

            // Check out the subdirectories
            string[] subdirectories = Directory.GetDirectories(node.Path);
            foreach (string subdirectory in subdirectories)
            {
                string relativePath = subdirectory.Substring(node.Path.Length + 1);
                DirectoryNode directoryNode = new DirectoryNode();
                directoryNode.Path = subdirectory;
                directoryNode.Name = relativePath;                

                EnumerateContent(directoryNode);
                if (directoryNode.ContentNodes.Count > 0 || directoryNode.DirectoryNodes.Count > 0)
                    node.DirectoryNodes.Add(directoryNode);
            }
        }

        /// <summary>
        /// Attempts to load the specified content file using the ContentManager.
        /// Returns the loaded object if successful, null otherwise.
        /// </summary>
        /// <param name="contentFile">The content file to load</param>
        /// <returns>the loaded object if successful, null otherwise</returns>
        private object TryLoadContent(string contentFile)
        {
            try
            {
                return contentManager.Load<object>(contentFile);
            }
            catch (ContentLoadException)
            {
                return null;
            }
        }

        private string GetImageKey(Type contentType)
        {
            string imageKey = "Default";

            if (contentType == typeof(Model))
                imageKey = "Model";
            else if (contentType == typeof(Effect))
                imageKey = "Effect";
            else if (contentType == typeof(Model))
                imageKey = "Model";
            else if (contentType == typeof(SpriteFont))
                imageKey = "SpriteFont";
            else if (contentType == typeof(Texture)
                    || contentType == typeof(Texture2D)
                    || contentType == typeof(Texture3D)
                    || contentType == typeof(TextureCube))
                imageKey = "Texture";

            return imageKey;
        }

        private TreeNode MakeTreeNode(DirectoryNode directoryNode)
        {
            TreeNode treeNode = new TreeNode(directoryNode.Name);
            treeNode.Tag = directoryNode;
            treeNode.ImageKey = "FolderClosed";
            treeNode.SelectedImageKey = "FolderClosed";
            treeNode.Tag = directoryNode;

            return treeNode;
        }        

        private TreeNode MakeTreeNode(ContentNode contentNode)
        {
            TreeNode treeNode = new TreeNode(contentNode.Name);
            treeNode.Tag = contentNode;
            treeNode.ImageKey = GetImageKey(contentNode.Type);
            treeNode.SelectedImageKey = treeNode.ImageKey;

            return treeNode;
        }

        /// <summary>
        /// Builds the TreeView nodes from the tree of enumerated content and directories.
        /// </summary>
        /// <param name="nodeCollection">The TreeNodeCollection that new nodes should be added to</param>
        /// <param name="directoryNode">The DirectoryNode representing the directory being added to the tree</param>
        private void BuildTree(TreeNodeCollection nodeCollection, DirectoryNode directoryNode)
        {
            nodeCollection.Clear();

            TreeNode treeNode = MakeTreeNode(directoryNode);
            nodeCollection.Add(treeNode);

            foreach (DirectoryNode node in directoryNode.DirectoryNodes)
                BuildTree(treeNode.Nodes, node);

            foreach (ContentNode node in directoryNode.ContentNodes)
            {
                if (node.Type == contentType || node.Type.IsSubclassOf(contentType))
                {
                    TreeNode contentTreeNode = MakeTreeNode(node);
                    treeNode.Nodes.Add(contentTreeNode);
                }
            }
        }        

        /// <summary>
        /// Called after a node is selected in the TreeView
        /// </summary>
        /// <param name="sender">The TreeView that raised the event</param>
        /// <param name="e">Event arguments</param>
        private void treeView_AfterSelect(object sender, TreeViewEventArgs e)
        {
            SelectedNode = e.Node.Tag as BaseNode;
        }

        /// <summary>
        /// Called after a node is expanded in the TreeView
        /// </summary>
        /// <param name="sender">The TreeView that raised the event</param>
        /// <param name="e">Event arguments</param>
        private void treeView_AfterExpand(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag is DirectoryNode)
            {
                e.Node.ImageKey = "FolderOpen";
                e.Node.SelectedImageKey = e.Node.ImageKey;
            }
        }

        /// <summary>
        /// Called after a node is collapsed in the TreeView
        /// </summary>
        /// <param name="sender">The TreeView that raised the event</param>
        /// <param name="e">Event arguments</param>
        private void treeView_AfterCollapse(object sender, TreeViewEventArgs e)
        {
            if (e.Node.Tag is DirectoryNode)
            {
                e.Node.ImageKey = "FolderClosed";
                e.Node.SelectedImageKey = e.Node.ImageKey;
            }
        }

        public event EventHandler LoadingComplete;
        public event EventHandler SelectedContentChanged;
        public event EventHandler SelectedNodeChanged;

        
    }

    /// <summary>
    /// Stores information about a directory in the content folder, as well
    /// as content files that belong to that particular directory.
    /// </summary>
    public class DirectoryNode : BaseNode
    {
        public List<DirectoryNode> DirectoryNodes = new List<DirectoryNode>();
        public List<ContentNode> ContentNodes = new List<ContentNode>();
    }

    /// <summary>
    /// Stores information about a particular content file
    /// </summary>
    public class ContentNode : BaseNode
    {
        public Type Type;
    }

    public class BaseNode
    {
        public string Name;
        public string Path;
    }
}
